//pretty cool stuff!
package gsp420.game;

import gsp420.math.V2D;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Vector;

public class Game
{
	private Board b = new Board();
	private int inputBuffer;
	private boolean running;
	private Vector<Player> players = new Vector<Player>();
	private Vector<Goal> goals = new Vector<Goal>();
	private static final int MAP = -1, TOKEN = 1;
	/** what is the current player moving, the map, themselves,, or other? */
	private int moveState;
	/** who's turn is it to move right now? */
	private int currentPlayer;
	private V2D drawOffset = new V2D(50, 50);
	/** which tile the user has clicked on last */
	private Tile clickedTile = null;
	/** the tile controlled by the player during tile placement */
	private Tile floatingTile;
	/** how big to draw the tiles in pixels */
	private int m_sqW, m_sqH;
	
	/** @return if the game should still be running */
	public boolean isRunning()
	{
		return running;
	}
	/** @return the tile clicked on */
	public Tile getTileAt(V2D mouseClick)
	{
		int col = (int)((mouseClick.x - drawOffset.x)/(Rules.TILE_SIZE*m_sqW));
		int row = (int)((mouseClick.y - drawOffset.y)/(Rules.TILE_SIZE*m_sqH));
		if(row >= 0 && row < b.board.length
		&& col >= 0 && col < b.board[0].length)
		{
			return b.board[row][col];
		}
		if(floatingTile.col == col && floatingTile.row == row)
		{
			return floatingTile;
		}
		return null;
	}
	/** @return the player location clicked on */
	public V2D getGamePosition(V2D mouseClick)
	{
		int col = (int)((mouseClick.x - drawOffset.x)/(m_sqW));
		int row = (int)((mouseClick.y - drawOffset.y)/(m_sqH));
		return new V2D(col, row);
	}
	/** @param e from {@link MouseListener} methods */
	public void handleMouse(MouseEvent e)
	{
		Tile t = getTileAt(new V2D(e.getX(), e.getY()));
		if(t != null)
		{
			clickedTile = t;
		}
	}

	/**
	 * @param a_squareWidth how wide, in pixels, to make each square in each tile
	 * @param a_squareHeight how tall, in pixels, to make each square in each tile
	 */
	public Game(Image[] playerImages, Image[] goalImages, int a_squareWidth, int a_squareHeight)
	{
		m_sqW = a_squareWidth;
		m_sqH = a_squareHeight;
		running = true;
		b.init();
		floatingTile = b.getUnusedTile();
		players.setSize(playerImages.length);
		players.set(0, new Player());
		players.get(0).init(playerImages[0], Color.red, 1, 1);
		players.set(1, new Player());
		players.get(1).init(playerImages[1], Color.green,
				Rules.BOARD_SIZE * Rules.TILE_SIZE - 2, 1);
		players.set(2, new Player());
		players.get(2).init(playerImages[2], Color.blue,
				Rules.BOARD_SIZE * Rules.TILE_SIZE - 2,
				Rules.BOARD_SIZE * Rules.TILE_SIZE - 2);
		players.set(3, new Player());
		players.get(3).init(playerImages[3], new Color(255, 200, 0), 1,
				Rules.BOARD_SIZE * Rules.TILE_SIZE - 2);
		goals.setSize(goalImages.length);
		Tile t;
		Vector<Goal> goalsToDistribute = new Vector<Goal>();
		for(int i = 0; i < goals.size(); ++i)
		{
			t = b.tiles[i*(b.tiles.length/goals.size())+1];
			goals.set(i, new Goal());
			goals.get(i).init(goalImages[i], Color.lightGray, t.col*Rules.TILE_SIZE+1, t.row*Rules.TILE_SIZE+1);
			goalsToDistribute.add(goals.get(i));
		}
		while(goalsToDistribute.size() > 0)
		{
			for(int i = 0; goalsToDistribute.size() > 0 && i < players.size(); ++i)
			{
				int r = (int)(Math.random()*goalsToDistribute.size());
				players.get(i).goals.add(goalsToDistribute.remove(r));
			}
		}
		moveState = MAP;
		currentPlayer = 0;
	}
	private int playerDrawOrder = 0;
	public void draw(Graphics g)
	{
		g.setColor(Color.DARK_GRAY);
		b.draw(g, (int)drawOffset.x, (int)drawOffset.y, m_sqW, m_sqH);
		int x = floatingTile.col*Rules.TILE_SIZE*m_sqW;
		int y = floatingTile.row*Rules.TILE_SIZE*m_sqH;
		Player p = players.get(currentPlayer);
		if(moveState == MAP)
		{
			g.setColor(p.color);
		}
		floatingTile.draw(g, (int)drawOffset.x+x, (int)drawOffset.y+y, m_sqW, m_sqH);

		for(int i = 0; i < p.goals.size(); ++i)
		{
			x = (i*2+1)*m_sqW;
			y = m_sqH;
			if(i == p.goals.size()-1 && p.viewingTopCard)
			{
				p.goals.get(i).drawAtPixelLoc(g, x, y, m_sqW, m_sqH);
			}
			else
			{
				g.fillRect(x, y, m_sqW, m_sqH);
			}
		}

		if(moveState == TOKEN)
		{
			g.setColor(p.color);
			Tile t = b.getTileAt(p.x, p.y);
			x = t.col*Rules.TILE_SIZE*m_sqW;
			y = t.row*Rules.TILE_SIZE*m_sqH;
			t.draw(g, (int)drawOffset.x+x, (int)drawOffset.y+y, m_sqW, m_sqH);
		}
		if(clickedTile != null)
		{
			x = (int)(drawOffset.x+clickedTile.col*m_sqW*Rules.TILE_SIZE);
			y = (int)(drawOffset.y+clickedTile.row*m_sqH*Rules.TILE_SIZE);
			g.setColor(Color.cyan);
			clickedTile.draw(g, x, y, m_sqW, m_sqH);
		}
		Goal goal;
		for (int i = 0; i < goals.size(); ++i)
		{
			goal = goals.get(i);
			goal.draw(g, (int)drawOffset.x, (int)drawOffset.y, m_sqW, m_sqH);
//			System.out.println(goal.x+" "+goal.y);
		}
		for (int i = 0; i < players.size(); ++i)
		{
			p = players.get((i+playerDrawOrder)%players.size());
			p.draw(g, (int)drawOffset.x, (int)drawOffset.y, m_sqW, m_sqH);
//			System.out.println(p.x+" "+p.y);
		}
		playerDrawOrder = (playerDrawOrder + 1) % players.size();
	}

	public void handleInput(int a_input)
	{
		inputBuffer = a_input;
	}

	public void moveMap()
	{
		Tile t = floatingTile;
		int oldX = t.col;
		int oldY = t.row;
		boolean insertHappened = false;
		// process input from the user
		switch (inputBuffer)
		{
		case 'w':
		case KeyEvent.VK_UP:
			--t.row;
			break;
		case 'a':
		case KeyEvent.VK_LEFT:
			--t.col;
			break;
		case 's':
		case KeyEvent.VK_DOWN:
			++t.row;
			break;
		case 'd':
		case KeyEvent.VK_RIGHT:
			++t.col;
			break;
		case ' ':
			floatingTile.rotateCW();
			break;
		case '\n':
		case '\r':
			insertFloatingTileIntoMap();
			insertHappened = true;
			moveState = TOKEN;
			break;
		case 27:
			running = false;
			break;
		}
		if(!insertHappened
		&& t.col >= 0 && t.col < Rules.BOARD_SIZE
		&& t.row >= 0 && t.row < Rules.BOARD_SIZE)
		{
			t.col = oldX;
			t.row = oldY;
			insertFloatingTileIntoMap();
			insertHappened = true;
		}
		// if state is no longer valid
		if (!insertHappened
			&&  !( (t.col == -1 && t.row >= -1 && t.row < Rules.BOARD_SIZE + 1)// left
				|| (t.col == Rules.BOARD_SIZE && t.row >= -1 && t.row < Rules.BOARD_SIZE + 1)// right
				|| (t.row == -1 && t.col >= -1 && t.col < Rules.BOARD_SIZE + 1)// top
				|| (t.row == Rules.BOARD_SIZE && t.col >= -1 && t.col < Rules.BOARD_SIZE + 1)))
		{// bottom
			t.col = oldX;
			t.row = oldY;
			floatingTile.rotateCW();
		}
	}

	public void updatePlayer(int index, int a_ms)
	{
		Player p = players.get(index);
		byte oldX = p.x;
		byte oldY = p.y;

		p.update(a_ms);

		if (p.x < 0 || p.x >= Rules.BOARD_SIZE * Rules.TILE_SIZE || p.y < 0
				|| p.y >= Rules.BOARD_SIZE * Rules.TILE_SIZE
				|| b.getAt(p.x, p.y) == '#')
		{
			p.x = oldX;
			p.y = oldY;
		}
		if(index == currentPlayer && p.goals.size() > 0)
		{
			Goal g = p.goals.get(p.goals.size()-1);
			if(g.x == p.x && g.y == p.y)
			{
				p.goals.remove(p.goals.size()-1);
				currentPlayer++;
				currentPlayer %= players.size();
				moveState = MAP;
			}
		}
	}

	public void update(int a_ms)
	{
//		System.out.println("   ->"+inputBuffer);
		// deals with user input
		switch (inputBuffer)
		{
		case '1':
		case '2':
		case '3':
		case '4':
			currentPlayer = inputBuffer - '1';
			break;
		case '`':
			switch(moveState)
			{
			case MAP:
				moveState = TOKEN;
				break;
			case TOKEN:
				moveState = MAP;
				break;
			}
			break;
		case 'c':{
			Player p = players.get(currentPlayer);
			p.viewingTopCard = !p.viewingTopCard;
			}break;
		default:
			switch (moveState)
			{
			case MAP:
				moveMap();
				break;
			case TOKEN:
				if(inputBuffer == '\n')
				{
					moveState = MAP;
					currentPlayer++;
					if(currentPlayer >= players.size())
					{
						currentPlayer = 0;
					}
				}
				else
				{
					switch(inputBuffer)
					{
					case KeyEvent.VK_UP:	inputBuffer = 'w';	break;
					case KeyEvent.VK_LEFT:	inputBuffer = 'a';	break;
					case KeyEvent.VK_DOWN:	inputBuffer = 's';	break;
					case KeyEvent.VK_RIGHT:	inputBuffer = 'd';	break;
					}
					players.get(currentPlayer).nextMove = (char) inputBuffer;
				}
				break;
			}
		}
		// clear the input buffer
		inputBuffer = 0;
		// update the game world
		for (int i = 0; i < players.size(); ++i)
		{
			updatePlayer(i, a_ms);
		}
	}

	public void insertFloatingTileIntoMap()
	{
		Tile t = floatingTile;
		// do not shift in the corners.
		if ((t.col == -1 && t.row == -1)
				|| (t.col == -1 && t.row == Rules.BOARD_SIZE)
				|| (t.col == Rules.BOARD_SIZE && t.row == -1)
				|| (t.col == Rules.BOARD_SIZE && t.row == Rules.BOARD_SIZE))
			return;
		final int UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3;
		int shiftingDir = 0;
		if (t.col == -1)
			shiftingDir = RIGHT;
		if (t.row == -1)
			shiftingDir = DOWN;
		if (t.col == Rules.BOARD_SIZE)
			shiftingDir = LEFT;
		if (t.row == Rules.BOARD_SIZE)
			shiftingDir = UP;

		int dim = 0, dimValue = 0;
		int dir = 0;
		final int X_DIM = 1, Y_DIM = 2;
		switch (shiftingDir)
		{
		case RIGHT:
			dim = Y_DIM;
			dimValue = t.row;
			dir = 1;
			break;
		case DOWN:
			dim = X_DIM;
			dimValue = t.col;
			dir = 1;
			break;
		case LEFT:
			dim = Y_DIM;
			dimValue = t.row;
			dir = -1;
			break;
		case UP:
			dim = X_DIM;
			dimValue = t.col;
			dir = -1;
			break;
		}
		int E = Rules.BOARD_SIZE - 1;
		// figure out which row to shift
		if (dim == Y_DIM)
			floatingTile.col += dir;
		else
			floatingTile.row += dir;
		// move them in space
		for (int i = 0; i < Rules.BOARD_SIZE; ++i)
		{
			if (dim == Y_DIM)
				b.board[dimValue][i].col += dir;
			else
				b.board[i][dimValue].row += dir;
		}
		// grab the one that is going to be out before the shift
		Tile nextFloater = (dim == Y_DIM) ? (b.board[dimValue][((dir == 1) ? E
				: 0)]) : (b.board[((dir == 1) ? E : 0)][dimValue]);
		// move them in the logic
		for (int i = ((dir == 1) ? E : 0); ((dir == 1) ? (i > 0) : (i < E)); i += -dir)
		{
			if (dim == Y_DIM)
				b.board[dimValue][i] = b.board[dimValue][i + -dir];
			else
				b.board[i][dimValue] = b.board[i + -dir][dimValue];
		}
		// put the current floating tile into the new spot
		if (dim == Y_DIM)
			b.board[dimValue][((dir == 1) ? 0 : E)] = floatingTile;
		else
			b.board[((dir == 1) ? 0 : E)][dimValue] = floatingTile;
		// replace the floating tile pointer to the one that was pushed out
		floatingTile = nextFloater;
	}
}
